package genetica;

import genetica.algoritmoSeleccion.AlgoritmoSeleccion;
import genetica.cromosomas.Cromosoma;
import genetica.cromosomas.factorias.FactoriaCromosomas;
import genetica.cruce.AlgoritmoCruce;
import genetica.excepciones.ExcepcionGenetica;
import genetica.mutacion.AlgoritmoMutacion;

import java.util.ArrayList;
import java.util.Random;
import java.util.TreeSet;

public class AlgoritmoGenetico {

	// Precision 0.0001
	protected int generacion;
	protected int maxGeneraciones; // (~100)
	protected int tamPoblacion;
	protected Cromosoma elMejor;
	protected double pCruze;
	protected double pMutacion;
	protected Integer elitismo = null;
	// protected double precision; // (~0.0001)
	protected FactoriaCromosomas<Cromosoma> factoria;
	protected AlgoritmoSeleccion as;
	protected AlgoritmoMutacion am;
	protected AlgoritmoCruce ac;
	protected ArrayList<Cromosoma> pob;
	protected ArrayList<Cromosoma> nuevaPob;
	protected double media = 0.0;

	protected boolean pobalcionEvaluada = false;

	protected TreeSet<Cromosoma> elite;
	private Cromosoma elMejorGeneracion;

	public AlgoritmoGenetico(int tamPoblacion, int maxGeneracion,
			double pCruze, double pMutacion,
			FactoriaCromosomas<Cromosoma> factoria, AlgoritmoSeleccion as,
			AlgoritmoMutacion am, AlgoritmoCruce ac, Integer elitismo) {

		this.elitismo = elitismo;
		this.nuevaPob = new ArrayList<Cromosoma>();
		this.tamPoblacion = tamPoblacion;
		

		this.factoria = factoria;
		for (int i = 0; i < tamPoblacion; i++)
			this.nuevaPob.add(this.factoria.crea());

		this.generacion = 0;
		this.maxGeneraciones = maxGeneracion;
		this.pCruze = pCruze;
		this.pMutacion = pMutacion;
		this.as = as;
		this.am = am;
		this.ac = ac;
		this.elMejor = null;
		this.am.setPMutacion(pMutacion);
		// this.precision = precision;
	}

	public int getGeneracion() {
		return generacion;
	}

	public void setGeneracion(int generacion) {
		this.generacion = generacion;
	}

	public int getMaxGeneraciones() {
		return maxGeneraciones;
	}

	public void setMaxGeneraciones(int maxGeneraciones) {
		this.maxGeneraciones = maxGeneraciones;
	}

	public int getTamPoblacion() {
		return tamPoblacion;
	}

	public void setTamPoblacion(int tamPoblacion) {
		this.tamPoblacion = tamPoblacion;
	}

	public Cromosoma getElMejor() {
		return elMejor;
	}

	public void setElMejor(Cromosoma elMejor) {
		this.elMejor = elMejor;
	}

	public double getpCruze() {
		return pCruze;
	}

	public void setpCruze(double pCruze) {
		this.pCruze = pCruze;
	}

	public double getpMutacion() {
		return pMutacion;
	}

	public void setpMutacion(double pMutacion) {
		this.pMutacion = pMutacion;
	}

	public double getElitismo() {
		return elitismo;
	}

	public void setElitismo(Integer elitismo) {
		this.elitismo = elitismo;
	}

	public FactoriaCromosomas<Cromosoma> getFactoria() {
		return factoria;
	}

	public void setFactoria(FactoriaCromosomas<Cromosoma> factoria) {
		this.factoria = factoria;
	}

	public AlgoritmoSeleccion getAs() {
		return as;
	}

	public void setAs(AlgoritmoSeleccion as) {
		this.as = as;
	}

	public ArrayList<Cromosoma> getPob() {
		return pob;
	}

	public void setPob(ArrayList<Cromosoma> pob) {
		this.pob = pob;
	}

	public void aumentaGeneracion() {
		this.generacion++;
	}

	public void evaluarPoblacion() throws ExcepcionGenetica {
		double punt_acum = 0;
		double aptitud_mejor = 0;
		double sum_aptitud = 0;
		int pos_mejor = 0;
		pob = nuevaPob;
		if ((elitismo < 0 || elitismo >= pob.size()))
			throw new Error(
					"El elitismo tiene que ser mayor que 0 ni mayor que la poblacion: elite="
							+ elitismo + ", tamaño=" + pob.size());
		int tamElite;

		double suelo = Double.MAX_VALUE;
		double techo = -Double.MAX_VALUE;
		for (int i = 0; i < tamPoblacion; i++) {

			pob.get(i).calcularFenotipo();
			pob.get(i).evalua();
			this.pobalcionEvaluada = true;
			if (pob.get(i).getAptitud() > techo)
				techo = pob.get(i).getAptitud();
			if (pob.get(i).getAptitud() < suelo)
				suelo = pob.get(i).getAptitud();

		}
		Cromosoma.setSuelo(suelo);
		Cromosoma.setTecho(techo);

		if (elitismo > 0 && elite != null) {
			tamElite = elite.size();
			TreeSet<Cromosoma> peores = new TreeSet<Cromosoma>();
			for (int i = 0; i < tamPoblacion; i++) {
				Cromosoma candidato = pob.get(i);
				Cromosoma mejor = null;
				if (peores.size() == 0) {
					mejor = candidato;
				} else
					mejor = peores.last();

				if (peores.size() < tamElite)
					peores.add(candidato);
				else if (candidato.compareTo(mejor) < 0) {
					peores.pollLast();
					// no tiene que ser borrado porque va a ser eliminado
					peores.add(candidato);
				}

			}
			for (int j = 0; j < tamElite; j++) {
				Cromosoma peor = peores.pollFirst();
				Cromosoma elMejor = elite.pollFirst();
				pob.remove(peor);
				pob.add(elMejor);
			}
		}
		media = 0.0;
		// nuevaPob = new ArrayList<Cromosoma>();
		for (int i = 0; i < tamPoblacion; i++) {

			sum_aptitud = sum_aptitud + pob.get(i).getAptitudNormalizada();

			if (pob.get(i).getAptitudNormalizada() > aptitud_mejor) {
				pos_mejor = i;
				aptitud_mejor = pob.get(i).getAptitudNormalizada();
			}
			media += pob.get(i).getAptitud();
		}

		for (int i = 0; i < tamPoblacion; i++) {
			pob.get(i).setPuntuacion(
					pob.get(i).getAptitudNormalizada() / sum_aptitud);
			pob.get(i).setPuntAcum(pob.get(i).getPuntuacion() + punt_acum);
			punt_acum = punt_acum + pob.get(i).getPuntuacion();
		}
		// A veces la punt_acumulada es un poco menor a 1, el último lo menos en
		// 1
		if (punt_acum < 1) {
			pob.get(tamPoblacion - 1).setPuntAcum(1.0);
		}
		this.setElMejorGeneracion(pob.get(pos_mejor));
		if (elMejor == null) {
			this.elMejor = pob.get(pos_mejor);
		}
		// actitud_mejor es referente al mejor de la generacion elMejor es el
		// mejor golbal
		else if (aptitud_mejor > elMejor.getAptitudNormalizada()) {
			elMejor = pob.get(pos_mejor);
		}

		media = media / tamPoblacion;
	}

	public void incrGeneracion() {
		this.generacion++;
	}

	public void seleccion() {
		if (elitismo > 0) {
			if (elitismo < 0 || elitismo >= pob.size())
				throw new Error(
						"El elitismo tiene que ser mayor que 0 ni mayor que la poblacion: elite="
								+ elitismo + ", tamaño=" + pob.size());
			elite = new TreeSet<Cromosoma>();
			// desenrollamos el bucle para que sea mas eficiente
			// B
			for (int i = 0; i < elitismo; i++) {
				elite.add(factoria.copia(pob.get(i)));
			}
			// C comprobar que realmente hay dos porque si ya has ay
			for (int i = elitismo; i < pob.size(); i++) {
				Cromosoma candidato = pob.get(i);
				Cromosoma peor = elite.first();
				// TODO comprobar que el compare lo hace bien

				if (peor.compareTo(candidato) < 0) {
					elite.pollFirst();
					elite.add(factoria.copia(candidato));
				}
				elitismo = elite.size();

			}
		}
		this.nuevaPob = as.seleccion(pob, this.factoria);

	}

	public void cruce() {
		Random r = new Random();
		ArrayList<Cromosoma> sel_cruce = new ArrayList<Cromosoma>(); // los que
																		// se //
																		// van a
																		// cruzar
		// Elegimos los individuos a cruzar
		for (int i = 0; i < tamPoblacion; i++) {
			double rand = r.nextDouble();
			if (rand < pCruze)
				sel_cruce.add(this.nuevaPob.get(i));
		}

		for (int i = 0; i < sel_cruce.size() - 1; i += 2) { // No hace falta si
															// son impares
															// porque lo
															// incrementamos de
															// dos en dos
			ac.cruce(sel_cruce.get(i), sel_cruce.get(i + 1));
		}
	}

	public void mutacion() {

		for (int i = 0; i < tamPoblacion; i++) {
			am.mutacion(nuevaPob.get(i));
		}
	}

	public boolean terminado() {
		return !(this.generacion < this.maxGeneraciones);
	}

	public double getMedia() {
		return media;
	}

	public void setElMejorGeneracion(Cromosoma elMejorGeneracion) {
		this.elMejorGeneracion = elMejorGeneracion;
	}

	public Cromosoma getElMejorGeneracion() {
		return elMejorGeneracion;
	}
}
